package avicit.bdp.oss.service.bucket;

import avicit.bdp.common.base.BaseService;
import avicit.bdp.common.utils.BdpLogUtil;
import avicit.bdp.common.utils.ConfigUtils;
import avicit.bdp.common.utils.file.MinioUtils;
import avicit.bdp.oss.dao.BucketDao;
import avicit.bdp.oss.dto.BucketDto;
import avicit.bdp.oss.dto.BucketStorageStatsQueryDto;
import avicit.bdp.oss.dto.SysStatsQureyDto;
import avicit.bdp.oss.service.auth.ObjectUserRelService;
import avicit.bdp.oss.utils.auth.PermissionCheckUtils;
import avicit.bdp.oss.utils.auth.StrategyEnum;
import avicit.bdp.oss.utils.common.AcitonEnum;
import avicit.bdp.oss.utils.common.CheckParaUtils;
import avicit.bdp.oss.utils.common.Constants;
import avicit.bdp.oss.utils.stats.BucketMetricStats;
import avicit.bdp.oss.utils.stats.Capacity;
import avicit.bdp.oss.utils.stats.MathUtils;
import avicit.bdp.oss.utils.stats.MetricTypeEnum;
import avicit.bdp.oss.utils.stats.ReqPara;
import avicit.platform6.api.system.ConvertColumnClient;
import avicit.platform6.api.system.impl.SystemConstant;
import avicit.platform6.commons.utils.BusinessUtil;
import avicit.platform6.commons.utils.ComUtil;
import avicit.platform6.core.context.ThreadContextHelper;
import avicit.platform6.core.exception.BusinessException;
import avicit.platform6.core.rest.msg.QueryRespBean;
import com.alibaba.fastjson2.JSONObject;
import com.github.pagehelper.Page;
import io.minio.MinioClient;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import static avicit.bdp.oss.utils.stats.MetricTypeEnum.MONTH_GET_REQ;
import static avicit.bdp.oss.utils.stats.MetricTypeEnum.MONTH_PUT_REQ;
import static avicit.bdp.oss.utils.stats.MetricTypeEnum.MONTH_READ_CAPACITY;
import static avicit.bdp.oss.utils.stats.MetricTypeEnum.MONTH_WRITE_CAPACITY;
import static avicit.bdp.oss.utils.stats.MetricTypeEnum.TODAY_GET_REQ;
import static avicit.bdp.oss.utils.stats.MetricTypeEnum.TODAY_PUT_REQ;
import static avicit.bdp.oss.utils.stats.MetricTypeEnum.TODAY_READ_CAPACITY;
import static avicit.bdp.oss.utils.stats.MetricTypeEnum.TODAY_WRITE_CAPACITY;

/**
 * @金航数码科技有限责任公司
 * @作者：developer
 * @邮箱：developer@avic-digital.com
 * @创建时间：2020-11-12
 * @类说明：BucketService
 * @修改记录：
 * @注意事项：
 */
@Service
@Transactional
public class BucketService extends BaseService<BucketDao, BucketDto> {
  private static final Logger logger = LoggerFactory.getLogger(BucketService.class);

  /** Minio连接信息 */
  private String endPoint;
  private String accessKey;
  private String secretKey;

  /** Bucket的DAO层 */
  @Autowired private BucketDao bucketDao;

  @Autowired private ObjectUserRelService objectUserRelService;

  /** Minio客户端 */
  private MinioClient minioClient;

  /** 获取系统用户名 */
  @Autowired private ConvertColumnClient convertColumnClient;

  /**
   * 创建桶
   *
   * @param bucketDto
   */
  public void makeBucket(BucketDto bucketDto) {
    // step1:合法性检查
    CheckParaUtils.checkObjectValidity(bucketDto, logger, "创建桶失败，参数为null.");
    CheckParaUtils.checkStringValidity(bucketDto.getBucketName(), logger, "创建桶失败，桶名称为null.");
    CheckParaUtils.checkObjectValidity(
        StrategyEnum.getStrategyType(bucketDto.getBucketStrategy()),
        logger,
        "创建桶失败，桶策略非法，只能是private/public_read/public_read_write");
    checkBucketNameValid(bucketDto.getBucketName());
    checkBucketExist(bucketDto.getBucketName());

    // step2:元数据持久化
    // step2-1:bucket元数据持久化
    String bucketId = ComUtil.getId();
    BucketDto newBucketDto =
        BucketDto.builder()
            .id(bucketId)
            .bucketName(bucketDto.getBucketName())
            .bucketStrategy(bucketDto.getBucketStrategy())
            .bucketMetricStats(JSONObject.toJSONString(new BucketMetricStats()))
            .build();
    insert(newBucketDto);
    // step2-2:桶-对象映射关系持久化
    this.objectUserRelService.saveBucketUsrRel(bucketId, bucketDto.getBucketName());

    // step3:创建bucket
    MinioUtils.makeBucket(getMinioClient(), bucketDto.getBucketName());

    // step4:记录平台日志
    BdpLogUtil.log4Insert(newBucketDto);
  }

  /**
   * 修改桶策略
   *
   * @param bucketDto
   */
  public void updateBucketStrategy(BucketDto bucketDto) {
    // step1:合法性检查
    CheckParaUtils.checkObjectValidity(bucketDto, logger, "修改桶策略失败，参数为null.");
    CheckParaUtils.checkStringValidity(bucketDto.getId(), logger, "修改桶策略失败，桶ID为null.");
    CheckParaUtils.checkObjectValidity(
        StrategyEnum.getStrategyType(bucketDto.getBucketStrategy()),
        logger,
        "修改桶策略失败，桶策略非法，只能是private/private_read/public_read_write");

    // step2:获取元数据信息
    BucketDto queryBucketRet = queryBucket(bucketDto.getId());

    // step3:权限、密级检查
    PermissionCheckUtils.checkPerm(queryBucketRet, null, null, AcitonEnum.UPDATE_BUCKET_STRATEGY);

    // step4:元数据持久化
    queryBucketRet.setBucketStrategy(bucketDto.getBucketStrategy());
    updateByPrimaryKey(queryBucketRet);

    // step5:修改minio桶策略
    MinioUtils.setBucketStrategy(
        getMinioClient(), queryBucketRet.getBucketName(), bucketDto.getBucketStrategy());

    // step6:记录平台日志
    BdpLogUtil.log4Update(queryBucketRet, bucketDto);
  }

  /**
   * 删除桶
   *
   * @param bucketDto
   */
  public void removeBucket(BucketDto bucketDto) {
    // step1:合法性检查
    CheckParaUtils.checkObjectValidity(bucketDto, logger, "删除失败，参数为null.");
    CheckParaUtils.checkStringValidity(bucketDto.getId(), logger, "删除失败，桶ID为null.");

    String[] ids = bucketDto.getId().split(Constants.COMMA);
    for (String currId : ids) {
      // step2:获取元数据信息
      BucketDto queryBucketRet = queryBucket(currId);

      // step3:权限、密级检查
      PermissionCheckUtils.checkPerm(queryBucketRet, null, null, AcitonEnum.REMOVE_BUCKET);

      // step4:删除桶元数据
      // step4-1:删除桶信息元数据
      deleteByPrimaryKey(currId);
      // step4-2:删除桶-用户映射关系
      this.objectUserRelService.delBucketUsrRel(currId, queryBucketRet.getBucketName());

      // step5:调用OSS存储集群接口，删除桶
      MinioUtils.removeBucket(getMinioClient(), queryBucketRet.getBucketName());

      // step6:记录平台日志
      BdpLogUtil.log4Delete(queryBucketRet);
    }
  }

  /**
   * 分页/模糊查询桶(注意：当前不包括权限校验)
   *
   * @param sort
   * @param bucketName
   * @param pageNo
   * @param pageSize
   * @return
   */
  public QueryRespBean<BucketDto> getBucketList(
      Integer sort, String bucketName, Integer pageNo, Integer pageSize) {
    // step1:合法性检查
    if (sort == null) {
      sort = new Integer(1);
    }
    if (sort < Constants.ORDERED_BY_MODIFIED_TIME_DESC
        || sort > Constants.ORDERED_BY_BUCKET_NAME_DESC) {
      CheckParaUtils.printLogAndThrowException(logger, String.format("查询桶列表时，参数sort[%s]不合法", sort));
    }
    if (pageNo <= 0 || pageSize <= 0) {
      CheckParaUtils.printLogAndThrowException(
          logger, String.format("查询桶列表时，参数pageNo/pageSize[%s,%s]不合法", pageNo, pageSize));
    }

    // step2:构建排序参数
    QueryRespBean<BucketDto> queryRespBean = new QueryRespBean<>();
    com.github.pagehelper.PageHelper.startPage(pageNo, pageSize);
    String orderField = buildOrderField(sort);
    String orderType = buildOrderType(sort);

    // step3:查表
    Page<BucketDto> bucketList =
        this.bucketDao.getBucketList(bucketName, getUserId(), orderField, orderType);
    queryRespBean.setResult(bucketList);

    // step4:处理查询结果，存储容量转为指定单位
    List<BucketDto> bucketDtos = bucketList.getResult();
    for (BucketDto tmpBucketDto : bucketDtos) {
      Capacity retCapacity = MathUtils.storageUnitProcess(tmpBucketDto.getCapacity());
      tmpBucketDto.setRetCapacity(retCapacity);
      tmpBucketDto.setCreatedBy(getUserNameByIds(tmpBucketDto.getCreatedBy()));
    }

    BdpLogUtil.log4Query(queryRespBean);
    return queryRespBean;
  }

  /**
   * 查询bucket名称是否重复
   *
   * @param bucketName
   * @return
   */
  public boolean checkBucketNameExist(String bucketName) {
    // step1:合法性检查
    CheckParaUtils.checkStringValidity(bucketName, logger, "查询桶名称是否重复失败，桶名称为null.");

    // step2:获取元数据信息，判断桶是否已存在
    BucketDto queryBucketRet = this.bucketDao.getBucketByName(bucketName);

    return queryBucketRet == null ? false : true;
  }

  /**
   * 获取系统统计信息，包括bucket总量、存储总量、对象总量
   *
   * @return
   */
  public SysStatsQureyDto getSysStats() {
    // step1:获取bucket列表
    String orderField = Constants.ORDERED_BY_LAST_UPDATE_TIME;
    String orderType = Constants.ORDERED_DESC;
    Page<BucketDto> bucketList =
        this.bucketDao.getBucketList(null, getUserId(), orderField, orderType);

    // step2：遍历bucket计算结果
    long bucketNum = bucketList.size();
    long totalCapacity = 0L;
    long totalObjectNum = 0L;
    for (BucketDto bucktDto : bucketList.getResult()) {
      totalObjectNum = totalObjectNum + bucktDto.getObjectNum();
      totalCapacity = totalCapacity + bucktDto.getCapacity();
    }

    return new SysStatsQureyDto(
        bucketNum, MathUtils.storageUnitProcess(totalCapacity), totalObjectNum, totalCapacity);
  }

  /**
   * 获取bucket存量统计信息，包括存储总量、对象总量
   *
   * @param bucketId
   * @return
   */
  public BucketStorageStatsQueryDto getBucketStorageStats(String bucketId) {
    // step1:合法性检查
    CheckParaUtils.checkStringValidity(bucketId, logger, "获取桶存储量统计信息失败，桶ID为null.");

    // step2:获取元数据信息
    BucketDto queryBucketRet = queryBucket(bucketId);

    // step3:权限、密级检查
    PermissionCheckUtils.checkPerm(queryBucketRet, null, null, AcitonEnum.GET_BUCKET_STORAGE_STATS);

    // step4:处理容量
    BucketStorageStatsQueryDto bucketStorageStatsQueryDto = new BucketStorageStatsQueryDto();
    bucketStorageStatsQueryDto.setTotalCapacity(
        MathUtils.storageUnitProcess(queryBucketRet.getCapacity()));
    bucketStorageStatsQueryDto.setTotalOjbectNum(
        new ReqPara(0L, queryBucketRet.getObjectNum(), Constants.NUM_UNIT));

    return bucketStorageStatsQueryDto;
  }

  /**
   * 获取bucket指标统计信息，包括本月读取量、本月写入量、本月PUT请求数、本月GET请求数、今日读取量、今日写入量、今日PUT请求数、今日GET请求数
   *
   * @param bucketId
   * @param metricType
   * @return
   */
  public Object getBucketMetricStats(String bucketId, MetricTypeEnum metricType) {
    // step1:合法性检查
    CheckParaUtils.checkStringValidity(bucketId, logger, "获取桶指标统计信息失败，桶ID为null.");

    // step2:获取元数据信息
    BucketDto queryBucketRet = queryBucket(bucketId);

    // step3:权限、密级检查
    PermissionCheckUtils.checkPerm(queryBucketRet, null, null, AcitonEnum.GET_BUCKET_METRIC_STATS);

    // step4:根据指标类型，返回对应指标信息
    Object metric = getBucketMetricByType(queryBucketRet, metricType);

    return metric;
  }

  /**
   * 对外接口，提供给其他模块适用
   *
   * @param bucketName
   * @return
   */
  public BucketDto getBucketByName(String bucketName) {
    return this.bucketDao.getBucketByName(bucketName);
  }

  /**
   * 对外接口，分页查询bucket信息
   *
   * @return
   */
  public Page<BucketDto> pageQueryBucket(int pageNum) {
    // step1:设置分页信息
    com.github.pagehelper.PageHelper.startPage(pageNum, Constants.MAX_PAGE_SIZE);

    // step2:分页查询
    String orderField = Constants.ORDERED_BY_LAST_UPDATE_TIME;
    String orderType = Constants.ORDERED_DESC;
    Page<BucketDto> bucketList = this.bucketDao.getBucketList(null, null, orderField, orderType);

    return bucketList;
  }

  /**
   * 获取系统统计信息，包括bucket总量、存储总量、对象总量
   *
   * @return
   */
  public String getVersion() {
    URLClassLoader cl = (URLClassLoader) (new BucketService()).getClass().getClassLoader();
    URL url = cl.findResource("META-INF/MANIFEST.MF");

    String version = "3.1.0";
    try {
      Manifest manifest = new Manifest(url.openStream());
      Attributes attributes = manifest.getMainAttributes();
      version = attributes.getValue("Implementation-Version");
    } catch (Exception e) {
      logger.error("从对象存储jar包中获取版本失败，errMsg=" + e.getMessage());
    }

    return version;
  }

  /**
   * 校验桶是否存在
   *
   * @param bucketName
   */
  private void checkBucketExist(String bucketName) {
    BucketDto queryBucketRet = this.bucketDao.getBucketByName(bucketName);
    CheckParaUtils.checkObjectNull(
        queryBucketRet, logger, String.format("创建桶[%s]失败，桶已存在.", bucketName));
  }

  /**
   * 校验bucket名称合法性
   *
   * @param bucketName
   */
  private void checkBucketNameValid(String bucketName) {
    if (!MinioUtils.isValidBucketName(bucketName)) {
      String message =
          String.format(
              "创建桶[%s]失败，(1)桶名称长度在[3,63](2)只能是小写字母/数字/点./连字符-组成，且必须以字母或数字开头结尾(3)"
                  + "桶名称不能格式化为IP地址(例如，xxx.xxx.xxx.xxx)",
              bucketName);
      logger.error(message);
      throw new BusinessException(message);
    }
  }

  /**
   * 查询BucketDto
   *
   * @param id
   * @return
   */
  private BucketDto queryBucket(String id) {
    BucketDto queryBucketRet = this.selectByPrimaryKey(id);
    CheckParaUtils.checkObjectValidity(
        queryBucketRet, logger, String.format("修改桶策略失败，桶[%s]不存在.", id));

    return queryBucketRet;
  }

  /**
   * 构建orderField
   *
   * @param sort
   * @return
   */
  private String buildOrderField(Integer sort) {
    // 排序，1:按修改时间倒序、2:按修改时间升序、3:按对象名称升序、4:按对象名称倒序
    String orderField = Constants.ORDERED_BY_LAST_UPDATE_TIME;
    if (sort != null) {
      if (Constants.ORDERED_BY_MODIFIED_TIME_DESC == sort) {
        orderField = Constants.ORDERED_BY_LAST_UPDATE_TIME;
      } else if (Constants.ORDERED_BY_MODIFIED_TIME_ASC == sort) {
        orderField = Constants.ORDERED_BY_LAST_UPDATE_TIME;
      } else if (Constants.ORDERED_BY_BUCKET_NAME_ASC == sort) {
        orderField = Constants.ORDERED_BY_BUCKET_NAME;
      } else if (Constants.ORDERED_BY_BUCKET_NAME_DESC == sort) {
        orderField = Constants.ORDERED_BY_BUCKET_NAME;
      } else {
        orderField = Constants.ORDERED_BY_LAST_UPDATE_TIME;
      }
    }

    return orderField;
  }

  /**
   * 构建orderType
   *
   * @param sort
   * @return
   */
  private String buildOrderType(Integer sort) {
    // 排序，1:按修改时间倒序、2:按修改时间升序、3:按对象名称升序、4:按对象名称倒序
    String orderType = Constants.ORDERED_DESC;
    if (sort != null) {
      if (Constants.ORDERED_BY_MODIFIED_TIME_DESC == sort) {
        orderType = Constants.ORDERED_DESC;
      } else if (Constants.ORDERED_BY_MODIFIED_TIME_ASC == sort) {
        orderType = Constants.ORDERED_ASC;
      } else if (Constants.ORDERED_BY_BUCKET_NAME_ASC == sort) {
        orderType = Constants.ORDERED_ASC;
      } else if (Constants.ORDERED_BY_BUCKET_NAME_DESC == sort) {
        orderType = Constants.ORDERED_DESC;
      } else {
        orderType = Constants.ORDERED_DESC;
      }
    }

    return orderType;
  }

  /**
   * 获取minio连接信息
   *
   * @return
   */
  private MinioClient getMinioClient() {
    if (minioClient == null) {
      endPoint = ConfigUtils.getInstance().getString("minio.endPoint.url");
      accessKey = ConfigUtils.getInstance().getString("minio.username");
      secretKey = ConfigUtils.getInstance().getString("minio.passward");
      minioClient = MinioClient.builder().endpoint(endPoint).credentials(accessKey, secretKey).build();
    }

    return minioClient;
  }

  /**
   * 获取当前登录用户信息
   *
   * @return
   */
  private String getUserId() {
    return ThreadContextHelper.getUserId() == null
        ? Constants.DEFALUT_USER_ID
        : ThreadContextHelper.getUserId();
  }

  /**
   * 根据bucket指标类型，获取指标信息
   *
   * @param bucketDto
   * @param metricType
   * @return
   */
  private BucketMetricStats getBucketMetricByType(BucketDto bucketDto, MetricTypeEnum metricType) {
    BucketMetricStats bucketMetricStats =
        JSONObject.parseObject(bucketDto.getBucketMetricStats(), BucketMetricStats.class);

    switch (metricType) {
      case MONTH_READ_CAPACITY:
        bucketMetricStats.setMetricTypeEnum(MONTH_READ_CAPACITY);
        break;
      case MONTH_WRITE_CAPACITY:
        bucketMetricStats.setMetricTypeEnum(MONTH_WRITE_CAPACITY);
        break;
      case MONTH_PUT_REQ:
        bucketMetricStats.setMetricTypeEnum(MONTH_PUT_REQ);
        break;
      case MONTH_GET_REQ:
        bucketMetricStats.setMetricTypeEnum(MONTH_GET_REQ);
        break;
      case TODAY_READ_CAPACITY:
        bucketMetricStats.setMetricTypeEnum(TODAY_READ_CAPACITY);
        break;
      case TODAY_WRITE_CAPACITY:
        bucketMetricStats.setMetricTypeEnum(TODAY_WRITE_CAPACITY);
        break;
      case TODAY_PUT_REQ:
        bucketMetricStats.setMetricTypeEnum(TODAY_PUT_REQ);
        break;
      case TODAY_GET_REQ:
        bucketMetricStats.setMetricTypeEnum(TODAY_GET_REQ);
        break;
      default:
        logger.error("指标类型{}不正确", metricType);
        throw new BusinessException("指标类型不正确。");
    }

    return bucketMetricStats;
  }

  /**
   * 根据用户ID获取用户名
   *
   * @param userIds
   * @return
   * @throws Exception
   */
  private String getUserNameByIds(String userIds) {
    if (StringUtils.isEmpty(userIds)) {
      return null;
    }

    Map<String, Set<String>> convertFormData = new HashMap<>();
    String[] array = userIds.split(",");
    for (String userId : array) {
      BusinessUtil.createConvertSet(convertFormData, SystemConstant.USER, userId);
    }

    StringBuffer buf = new StringBuffer(Constants.BLACK);
    if (convertFormData.size() > 0) {
      // 获取请求结果
      Map<String, Map<String, String>> convertResultData =
          convertColumnClient.replace(convertFormData);
      for (String userId : array) {
        String name = BusinessUtil.convertFormat(convertResultData, SystemConstant.USER, userId);
        buf.append(name).append(Constants.COMMA);
      }
    }

    String names = buf.toString();
    if (names.length() >= 1) {
      names = names.substring(0, names.length() - 1);
    }
    return names;
  }
}
